package cn.yjh.utils;


import cn.yjh.annotation.AliasFor;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @DESC:
 * @AUTHOR: YouJinhua
 * @DATE: 2022-01-30 08:44:59
 * @VERSION: 1.0
 */
public class AnnotationElementUtils {

    /**
     * 获取指定元素的注解类型（若没有直接注解，则从元素其他注解的元注解上查找）
     *
     * @param element        可以包含注解的元素，如Field, Method等
     * @param annotationType 注解类型
     * @param <A>
     * @return
     */
    public static <A extends Annotation> A getMergeAnnotation(AnnotatedElement element, Class<A> annotationType) {

        A target = null;

        AnnotationNode headNode = null;

        AnnotationNode currentNode = null;

        List<AttributeNode> attributeCache = null;
        // 遍历查找该元素的其他注解上的元注解
        for (Annotation anno : element.getAnnotations()) {
            if (currentNode == null) {
                currentNode = headNode = new AnnotationNode(anno);
            } else {
                currentNode.next = new AnnotationNode(anno);
                currentNode.next.prev = currentNode;
            }
            if (anno.annotationType() == annotationType) {
                target = (A) anno;
                break;
            }

            // 如果从该元注解没有找到指定注解，则清空该访问链，继续该元素其他注解上查找
            if ((currentNode = findMetaAnnotation(anno, annotationType, currentNode)) == null) {
                // help gc
                currentNode = null;
                headNode = null;
                continue;
            }
            break;
        }
        // 如果注解不存在，直接return
        if (currentNode == null) {
            return null;
        }
        // 设置值
        target = (A) currentNode.target;
        // 查找属性访问链
        attributeCache = findAccessChain(headNode);
        // 校验属性访问链上的别名
        Map<String, Object> memberValues = validateAccessChain(headNode, attributeCache, element);
        // 设置属性值
        resetAttributeValue(target, memberValues);
        return target;
    }

    /**
     * 判断注解是否存在于AnnotatedElement上
     *
     * @param element
     * @param type
     * @return
     */
    public static boolean isAnnotationPresent(AnnotatedElement element, Class<? extends Annotation> type) {
        boolean result = false;
        Annotation[] annotations = element.getAnnotations();
        for (Annotation annotation : annotations) {
            String annotationName = annotation.annotationType().getName();
            if (!annotationName.startsWith("java") && !annotationName.startsWith("javax")) {
                if (annotation.annotationType() == type) {
                    result = true;
                } else {
                    result = isAnnotationPresent(annotation.annotationType(), type);
                }
            }
            if (result) break;
        }
        return result;
    }

    /**
     * 根据元注解类型递归查找指定注解的元注解
     *
     * @param annotation  注解
     * @param targetType  元注解类型
     * @param currentNode 访问链
     * @param <A>
     * @return
     */
    private static <A extends Annotation> AnnotationNode findMetaAnnotation(Annotation annotation, Class<A> targetType, AnnotationNode currentNode) {
        Class<? extends Annotation> clazz = annotation.annotationType();
        A a = clazz.getAnnotation(targetType);
        AnnotationNode nextNode = null;
        if (a == null) {
            for (Annotation metaAnn : clazz.getAnnotations()) {
                Class<? extends Annotation> metaAnnType = metaAnn.annotationType();
                // 如果是java自带的元注解，则跳过继续
                if (metaAnnType.getName().startsWith("java")) {
                    continue;
                }
                if (nextNode == null) {
                    nextNode = currentNode;
                    nextNode.next = new AnnotationNode(metaAnn);
                    nextNode.next.prev = nextNode;
                    nextNode = nextNode.next;
                }
                // 如果注解存在指定元注解，则跳出循环，否则，递归查找
                if ((a = metaAnnType.getAnnotation(targetType)) != null) {
                    nextNode.next = new AnnotationNode(a);
                    nextNode.next.prev = nextNode;
                    nextNode = nextNode.next;
                    break;
                }
                // 递归查找
                if ((nextNode = findMetaAnnotation(metaAnn, targetType, nextNode)) == null) {
                    continue;
                }
            }
        } else {
            nextNode = currentNode;
            nextNode.next = new AnnotationNode(a);
            nextNode.next.prev = nextNode;
            nextNode = nextNode.next;
        }
        if (a == null && nextNode != currentNode) {
            // help gc
            if (currentNode.prev != null) {
                currentNode.prev.next = null;
                currentNode.prev = null;
            }
        }
        return nextNode;
    }

    /**
     * 查找注解属性访问链
     *
     * @param annotationNode
     * @return
     */
    private static List<AttributeNode> findAccessChain(AnnotationNode annotationNode) {
        List<AttributeNode> nodeCache = new ArrayList<>(32);
        while (annotationNode != null) {
            Annotation target = annotationNode.target;
            Class<? extends Annotation> clazz = target.annotationType();
            List<Method> attributeMethods = getAttributeMethods(clazz);
            for (Method attributeMethod : attributeMethods) {
                String attributeName = attributeMethod.getName();
                Object defaultAttributeValue = null;
                try {
                    defaultAttributeValue = clazz.getDeclaredMethod(attributeName).getDefaultValue();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
                AttributeNode node = new AttributeNode(clazz, attributeName);
                nodeCache.add(node);
                node.defaultAttributeValue = defaultAttributeValue;
                if (node.prev == null) {
                    attributeMethod.setAccessible(true);
                    try {
                        node.attributeValue = attributeMethod.invoke(target, null);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                } else {
                    node.attributeValue = node.prev.attributeValue;
                }
                AttributeNode next = null;
                if (attributeMethod.isAnnotationPresent(AliasFor.class)) {
                    AliasFor aliasFor = attributeMethod.getAnnotation(AliasFor.class);
                    String aliasName = isEmpty(aliasFor.value()) ? aliasFor.attribute() : aliasFor.value();
                    Class<? extends Annotation> annotation = aliasFor.annotation();
                    // 判断是本类的别名还是其他类的别名
                    if (annotation == Annotation.class) {
                        // 如果是本类的别名，那么必须指定别名的名称
                        if (isEmpty(aliasName))
                            throw new RuntimeException("@AliasFor not have alias name");
                        annotation = clazz;
                    } else {
                        if (isEmpty(aliasName))
                            aliasName = attributeMethod.getName();
                    }
                    Method aliasMethod = null;
                    try {
                        aliasMethod = annotation.getDeclaredMethod(aliasName);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException("the @" + annotation.getSimpleName() + " does't has attribute: [" + aliasName + "]");
                    }
                    next = new AttributeNode(annotation, aliasName);
                    nodeCache.add(next);
                    next.attributeValue = node.attributeValue;
                    next.defaultAttributeValue = aliasMethod.getDefaultValue();
                    node.next = next;
                    if (next.prev == null)
                        next.prev = node;
                }
            }
            annotationNode = annotationNode.next;
        }
        return nodeCache;
    }

    /**
     * 校验属性访问链上所有的别名
     * @param headNode
     * @param attributeCache
     * @param element
     * @return
     */
    private static Map<String, Object> validateAccessChain(AnnotationNode headNode, List<AttributeNode> attributeCache, AnnotatedElement element) {
        Map<String, Object> memberValues = null;
        Annotation target = headNode.target;
        Map<? extends Class<? extends Annotation>, List<AttributeNode>> collect = attributeCache.stream().collect(Collectors.groupingBy(node -> node.target));
        // 循环校验访问链上的属性
        while (headNode != null) {
            memberValues = validateAliasFor(headNode, attributeCache, element, target);
            headNode = headNode.next;
        }
        return memberValues;
    }

    /**
     * 校验属性访问链上某一个层级上的别名
     * @param currentNode
     * @param attributeNodes
     * @param element
     * @param headAnnotation
     * @param <A>
     * @return
     */
    private static <A extends Annotation> Map<String, Object> validateAliasFor(AnnotationNode currentNode, List<AttributeNode> attributeNodes, AnnotatedElement element, Annotation headAnnotation) {
        Map<String, Object> memberValues = new HashMap<>();
        Class<? extends Annotation> clazz = currentNode.target.annotationType();
        Map<String, List<AttributeNode>> attributes = currentNode.attributes;
        Iterator<AttributeNode> iterator = attributeNodes.iterator();
        // 使用迭代器遍历，将当前层级的属性节点删除，提高下一层级遍历的速度
        while (iterator.hasNext()) {
            AttributeNode node = iterator.next();
            if (clazz == node.target) {
                String attributeName = node.attributeName;
                List<AttributeNode> nodeList = attributes.computeIfAbsent(attributeName, k -> new ArrayList<AttributeNode>());
                nodeList.add(node);
                iterator.remove();
            }
        }
        Set<Map.Entry<String, List<AttributeNode>>> entries = currentNode.attributes.entrySet();
        List<AttributeNode> validateList = new ArrayList<>();
        List<AttributeNode> skipList = new ArrayList<>();
        entries.forEach(entry -> {
            List<AttributeNode> nodes = entry.getValue();
            if (nodes.size() > 1) {
                nodes.forEach(node -> {
                    if (!isEquals(node.defaultAttributeValue, node.attributeValue)) {
                        validateList.add(node);
                        memberValues.put(node.attributeName, node.attributeValue);
                    }else{
                        skipList.add(node);
                    }
                });
            }
        });
        if (!CollectionUtils.isEmpty(validateList)) {
            Object attributeValue = validateList.get(0).attributeValue;
            // 设置下一级继承至跳过的那些属性的属性值
            skipList.forEach(node ->{
                if(node.next!=null){
                    node.next.attributeValue = attributeValue;
                }
            } );
        }
        int len = validateList.size();
        if (len > 1) {
            for (int i = 0; i < len; i++) {
                for (int j = i + 1; j < len; j++) {
                    AttributeNode node1 = validateList.get(i);
                    AttributeNode node2 = validateList.get(j);
                    if (isEquals(node1.attributeValue, node2.attributeValue))
                        continue;
                    String attributeName1 = findAttributeName(node1);
                    String attributeName2 = findAttributeName(node2);
                    throwsException(node1.attributeValue, node2.attributeValue, attributeName1, attributeName2, element, headAnnotation);
                }
            }
        }else{
            // 只指定了一个属性的值，而另一个却与当前指定值的属性互为别名的属性值
            if(!CollectionUtils.isEmpty(validateList)){
                Object attributeValue = validateList.get(0).attributeValue;
                skipList.forEach(node->{
                    memberValues.put(node.attributeName,attributeValue);
                });
            }
        }
        return memberValues;

    }

    private static String findAttributeName(AttributeNode node) {
        while (node.prev != null) {
            node = node.prev;
        }
        String attributeName = node.attributeName;
        return attributeName;
    }

    /**
     * 获取注解中的所有属性方法
     *
     * @param annotationType 注解类型
     * @return 属性方法列表
     */
    private static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) {
        List<Method> methods = new ArrayList<>();
        for (Method method : annotationType.getDeclaredMethods()) {
            methods.add(method);
        }
        return methods;
    }

    /**
     * 重新设置查找到的注解的属性值
     *
     * @param targetAnnotation
     * @param memberValues
     * @param <A>
     */
    private static <A extends Annotation> void resetAttributeValue(A targetAnnotation, Map<String, Object> memberValues) {
        Set<Map.Entry<String, Object>> entries = memberValues.entrySet();
        entries.forEach(entry -> {
            String key = entry.getKey();
            Object value = entry.getValue();
            setAttributeValue(targetAnnotation, key, value);
        });
    }

    /**
     * 判断注解的属性值是否相等
     *
     * @param o1
     * @param o2
     * @return
     */
    private static boolean isEquals(Object o1, Object o2) {
        boolean result = false;
        // 先判断两个对象是否同种类型
        if (o1.getClass().isInstance(o2)) {
            // 再判断是否是数组类型的，然后决定采用哪种方式比较
            if (o1.getClass().isArray()) {
                Object[] arr1 = (Object[]) o1;
                Object[] arr2 = (Object[]) o2;
                result = Arrays.equals(arr1, arr2);
            } else {
                result = o1.equals(o2);
            }
        }
        return result;
    }

    private static boolean isEmpty(String str) {
        return str == null || str.trim().length() == 0;
    }

    /**
     * 给某个注解的某个属性赋值
     *
     * @param an
     * @throws Exception
     */
    private static void setAttributeValue(Annotation an, String attributeName, Object value) {
        InvocationHandler handler = Proxy.getInvocationHandler(an);
        // 获取 AnnotationInvocationHandler 的 memberValues 字段
        try {
            Field handlerField = handler.getClass().getDeclaredField("memberValues");
            // 因为这个字段是 private final 修饰，所以要打开权限
            handlerField.setAccessible(true);
            // 获取 memberValues
            Map memberValues = (Map) handlerField.get(handler);
            memberValues.put(attributeName, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 抛出别名属性值不同的异常
     *
     * @param o1
     * @param o2
     * @param attributeName
     * @param aliasAttributeName
     * @param element
     * @param annotation
     */
    private static void throwsException(Object o1, Object o2, String attributeName, String aliasAttributeName, AnnotatedElement element, Annotation annotation) {
        String v1 = o1.toString();
        String v2 = o2.toString();
        if (o1.getClass().isArray()) {
            v1 = Arrays.toString((Object[]) o1);
            v2 = Arrays.toString((Object[]) o2);
        }
        String targetName = "";
        if (element instanceof Class) {
            targetName = ((Class) element).getName();
        }
        if (element instanceof Method) {
            targetName = ((Method) element).getName();
        }
        if (element instanceof Field) {
            targetName = ((Field) element).getName();
        }
        String msg = String.format(
                "Different @AliasFor values for annotation 【%s】 declared on 【%s】; attribute 【'%s'】 and its alias 【'%s'】 are declared with values of 【%s】 and 【%s】.",
                annotation.annotationType().getName(),
                targetName,
                attributeName,
                aliasAttributeName,
                v1,
                v2);
        throw new RuntimeException(msg);
    }

    /**
     * 属性节点描述类：这是一个双向链表
     */
    private final static class AttributeNode {

        // 当前属性所属的注解的类型
        protected Class<? extends Annotation> target;

        // 当前属性的默认值
        protected Object defaultAttributeValue;

        // 当前属性的值
        protected Object attributeValue;

        // 属性名称
        protected String attributeName;

        // 当前属性所指向的属性
        protected AttributeNode next;

        // 当前属性被指向的属性
        protected AttributeNode prev;

        protected AttributeNode(Class<? extends Annotation> target, String attributeName) {
            this.target = target;
            this.attributeName = attributeName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            AttributeNode node = (AttributeNode) o;
            return Objects.equals(target, node.target) && Objects.equals(attributeName, node.attributeName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(target, attributeName);
        }
    }

    private final static class AnnotationNode {

        protected Annotation target;

        protected Map<String, List<AttributeNode>> attributes = new HashMap<>();

        protected AnnotationNode next;

        protected AnnotationNode prev;

        public AnnotationNode(Annotation target) {
            this.target = target;
        }
    }
}
